/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
*/

package langhx;

import langhx.Ensure;

/**
    IMPORTANT: If the REMOVE_REQUIRE compilation flag is used, then these 
    methods have no effect.
    
    Used to "require" that the parameters passed to a function are within 
    certain conditions. This is generally used at the beginning of a function.
    .
    It contains methods that test the value to make sure that certain conditions
    have been upheld. If the condition isn't upheld, [RequireError] is thrown.
    
    Usage:
        Require.notNull(job, "job"); //the name given as a string is for 
                                     //debugging purposes
**/
class Require {
    
    /**
        Throws [RequireError] if the value is null.
    **/
    public static inline function notNull<T>(value : T, name : String) {
        #if !REMOVE_REQUIRE
        if (value == null) throw new RequireError(value, name, "Not null");
        #end
    }
    
    /**
        Throws [RequireError] if the given iterable isn't empty 
        (ie [value.iterator().hasNext() is true) or if the value itself is 
        null.
    **/
    public static inline function empty<T>(value : Iterable<T>, name : String) {
        #if !REMOVE_REQUIRE
        if (value == null || value.iterator().hasNext()) {
            throw new RequireError(value, name, "Empty");
        }
        #end
    }
    
    /**
        Throws [RequireError] if the given iterable is empty
        (ie [value.iterator().hasNext() is false) or if the value itself is 
        null.
    **/
    public static inline function notEmpty<T>(value : Iterable<T>, 
                                              name : String) {
        #if !REMOVE_REQUIRE
        if (value == null || !value.iterator().hasNext()) {
            throw new RequireError(value, name, "Not empty");
        }
        #end
    }
    
    /**
        Throws [RequireError] if the value is outside of the given range 
        (inclusive) or if the value is null.
        
        Works for both [Float] and [Int].
    **/
    public static inline function inRange(value : Null<Float>, lower : Float, 
                                          upper : Float, name : String) {
        #if !REMOVE_REQUIRE
        if (value == null || value < lower || value > upper) {
            throw new RequireError(value, name, "In range " + lower + " to " + 
                                                upper);
        }
        #end
    }
    
    /**
        Throws [RequireError] if the value is null or false.
    **/
    public static inline function isTrue(value : Null<Bool>, name : String) {
        if (value == null || !value) {
            throw new RequireError(value, name, "Is true");
        }
    }
    
    /**
        Throws [EnsureError] if the value is null or true.
    **/
    public static inline function isFalse(value : Null<Bool>, name : String) {
        if (value == null || value) {
            throw new RequireError(value, name, "Is false");
        }
    }
    
    #if LANG_HX_UNIT_TEST
    public static function main() {
        var runner = new haxe.unit.TestRunner();
        
        runner.add(new RequireTest());
        runner.add(new EnsureTest());
        
        runner.run();
    }
    #end
}

/**
    Thrown if one of the required conditions isn't met.
**/
class RequireError {
    
    private var name : String;
    private var expected : String;
    private var value : Dynamic;
    
    /**
        Takes the value given, the name of the parameter, and the 
        expected value.
    **/
    public function new(value : Dynamic, name : String, expected : String) {
        this.value = value;
        this.name = name;
        this.expected = expected;
    }
    
    /**
        Returns the name of the parameter.
    **/
    public inline function getName() : String {
        return name;
    }
    
    /**
        Returnst the expected conditon.
    **/
    public inline function getExpected() : String {
        return expected;
    }
    
    /**
        Returns the value given.
    **/
    public inline function getValue() : Dynamic {
        return value;
    }
    
    /**
        Returns:
            Requirement wasn't met.\n
            [expected] condition wasn't met for [name]. 
            The value given was [value].
    **/
    public function toString() : String {
        return "Requirement wasn't met.\n " + 
                expected + " condition wasn't met for " + name + ". \n" +
                "The value given was " + value + ".";
    }
}

#if LANG_HX_UNIT_TEST
import haxe.unit.TestCase;

class RequireTest extends TestCase {
    
    public function testNotNull() {
        try {
            Require.notNull(null, "nullTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.notNull(new Array<Int>(), "nullTest");
        assertTrue(true);
    }
    
    public function testEmpty() {
        try {
            Require.empty([1, 2, 3], "emptyTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        try {
            Require.empty(null, "emptyTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.empty(new Array<Int>(), "emptyTest");
        assertTrue(true);
    }
    
    public function testNotEmpty() {
        try {
            Require.notEmpty(new Array<Int>(), "notEmptyTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        try {
            Require.notEmpty(null, "notEmptyTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.notEmpty([1, 2, 3], "notEmptyTest");
        assertTrue(true);
    }
    
    public function testInRange() {
        try {
            Require.inRange(-0.5, 0.0, 1.0, "inRangeTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        try {
            Require.inRange(null, 0.0, 1.0, "inRangeTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.inRange(0.5, 0.0, 1.0, "inRangeTest");
        Require.inRange(0.0, 0.0, 1.0, "inRangeTest");
        Require.inRange(1.0, 0.0, 1.0, "inRangeTest");
        assertTrue(true);
    }
    
    public function testIsTrue() {
        try {
            Require.isTrue(false, "isTrueTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        try {
            Require.isTrue(null, "isTrueTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.isTrue(true, "isTrueTest");
        assertTrue(true);
    }
    
    public function testIsFalse() {
        try {
            Require.isFalse(true, "isFalseTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        try {
            Require.isTrue(null, "isFalseTest");
            assertTrue(false);
        } catch (e : RequireError) {}
        
        Require.isFalse(false, "isFalseTest");
        assertTrue(true);
    }
}
#end
